#include "libGeoutils/property/properties.h"
#include "libGeoutils/tool/uuid_tool.h"
#include "nlohmann/json.hpp"
#include <sstream>

#define UUID_KEY "UUID"

template<class T>
bool geo::prop::Properties::getProperty(const std::string& key, T& value) const 
{
	return true;
}

template<class T>
void geo::prop::Properties::setProperty(const std::string& key, const T& value)
{
	return true;
}

template<>
GEOUTILS_EXPORT bool geo::prop::Properties::getProperty<bool>(const std::string& key, bool& value) const
{
	if (mBoolProps.count(key)) {
		value = mBoolProps.at(key);
		return true;
	}
	return false;
}

template<>
GEOUTILS_EXPORT void geo::prop::Properties::setProperty<bool>(const std::string& key, const bool& value)
{
	if (!key.empty()) {
		mBoolProps[key] = value;
	}
}

template<>
GEOUTILS_EXPORT bool geo::prop::Properties::getProperty<int>(const std::string& key, int& value) const
{
	if (mIntProps.count(key)) {
		value = mIntProps.at(key);
		return true;
	}
	return false;
}

template<>
GEOUTILS_EXPORT void geo::prop::Properties::setProperty<int>(const std::string& key, const int& value)
{
	if (!key.empty()) {
		mIntProps[key] = value;
	}
}

template<>
GEOUTILS_EXPORT bool geo::prop::Properties::getProperty<double>(const std::string& key, double& value) const
{
	if (mDoubleProps.count(key)) {
		value = mDoubleProps.at(key);
		return true;
	}
	return false;
}

template<>
GEOUTILS_EXPORT void geo::prop::Properties::setProperty<double>(const std::string& key, const double& value)
{
	if (!key.empty()) {
		mDoubleProps[key] = value;
	}
}

template<>
GEOUTILS_EXPORT bool geo::prop::Properties::getProperty<std::string>(const std::string& key, std::string& value) const
{
	if (mStringProps.count(key)) {
		value = mStringProps.at(key);
		return true;
	}
	return false;
}

template<>
GEOUTILS_EXPORT void geo::prop::Properties::setProperty<std::string>(const std::string& key, const std::string& value)
{
	if (!key.empty()) {
		mStringProps[key] = value;
	}
}

geo::prop::Properties::Properties(bool haveUuid)
{
	if (haveUuid) {
		setProperty<std::string>(UUID_KEY, geo::tool::UUID_Identifier::CreateUUID());
	}
}

geo::prop::Properties::~Properties()
{
}

bool geo::prop::Properties::to_string(std::string& out_str) const
{
	std::stringstream ss;
	std::string break_char = "\n";
	std::string split_char = ": ";
	std::string mark_char = "\"";

	if (!mStringProps.empty()) {
		ss << "--- ( Type: string ) ---" << break_char;
		for (const auto& loop_string_prop : mStringProps) {
			ss << mark_char << loop_string_prop.first << mark_char << split_char
				<< mark_char << loop_string_prop.second << mark_char << break_char;
		} ss << break_char;
	}
	if (!mDoubleProps.empty()) {
		ss << "--- ( Type: double ) ---" << break_char;
		for (const auto& loop_double_prop : mDoubleProps) {
			ss << mark_char << loop_double_prop.first << mark_char << split_char
				<< loop_double_prop.second << break_char;
		} ss << break_char;
	}
	if (!mIntProps.empty()) {
		ss << "--- ( Type: int ) ---" << break_char;
		for (const auto& loop_int_prop : mIntProps) {
			ss << mark_char << loop_int_prop.first << mark_char << split_char
				<< loop_int_prop.second << break_char;
		} ss << break_char;
	}
	if (!mBoolProps.empty()) {
		ss << "--- ( Type: bool ) ---" << break_char;
		for (const auto& loop_bool_prop : mBoolProps) {
			ss << mark_char << loop_bool_prop.first << mark_char << split_char
				<< loop_bool_prop.second << break_char;
		} ss << break_char;
	}

	out_str = ss.str();
	return true;
}

bool geo::prop::Properties::to_json(std::string& out_str) const
{
	nlohmann::json json;
	for (const auto& loop_string_prop : mStringProps) {
		json[loop_string_prop.first] = loop_string_prop.second;
	}
	for (const auto& loop_double_prop : mDoubleProps) {
		json[loop_double_prop.first] = loop_double_prop.second;
	}
	for (const auto& loop_int_prop : mIntProps) {
		json[loop_int_prop.first] = loop_int_prop.second;
	}
	for (const auto& loop_bool_prop : mBoolProps) {
		json[loop_bool_prop.first] = loop_bool_prop.second;
	}

	out_str = json.dump();
	return true;
}

bool geo::prop::Properties::from_json(const std::string& in_str)
{
	try {
		nlohmann::json json = nlohmann::json::parse(in_str);
		for (nlohmann::json::iterator it = json.begin(); it != json.end(); ++it) {
			if (it.value().is_string()) {
				mStringProps[it.key()] = it.value().get<std::string>();
			}
			else if (it.value().is_number_float()) {
				mDoubleProps[it.key()] = it.value().get<double>();
			}
			else if (it.value().is_number_integer()) {
				mIntProps[it.key()] = it.value().get<int>();
			}
			else if (it.value().is_boolean()) {
				mBoolProps[it.key()] = it.value().get<bool>();
			}
		}
		return true;
	}
	catch (const std::exception& e) {
		mLastErrorMessage = e.what();
		return false;
	}
}
